package calao.storage;

import java.util.HashMap;
import java.util.Map;

/** Binds objects to field data.
 * As opposed to serialization this provides loose coupling between object classes and field data,
 * and thus allows for easier code refactoring.  
 */
public abstract class DataBinding<T>
extends SchemaElement
{
	
	final static HashMap<String,DataBinding> NAME_MAP = new HashMap<String,DataBinding>();
	final static HashMap<Class,DataBinding> CLASS_MAP = new HashMap<Class,DataBinding>();
	
	final static DataBinding<Integer> INTEGER = new DataBinding<Integer>(Integer.class) {
		public Integer readObject(DataReader reader, int version) {
			return reader.readInt();
		}
		public void write(DataWriter writer, Integer object) {
			writer.write(object.intValue());
		}
	};
	
	final static DataBinding<Long> LONG = new DataBinding<Long>(Long.class) {
		public Long readObject(DataReader reader, int version) {
			return reader.readLong();
		}
		public void write(DataWriter writer, Long object) {
			writer.write(object.longValue());
		}
	};

	final static DataBinding<Double> DOUBLE = new DataBinding<Double>(Double.class) {
		public Double readObject(DataReader reader, int version) {
			return reader.readDouble();
		}
		public void write(DataWriter writer, Double object) {
			writer.write(object.doubleValue());
		}
	};

	final static DataBinding<String> STRING = new DataBinding<String>(String.class) {
		public String readObject(DataReader reader, int version) {
			return reader.readString();
		}
		public void write(DataWriter writer, String object) {
			writer.write(object);
		}
	};

	final static DataBinding<Boolean> BOOLEAN = new DataBinding<Boolean>(Boolean.class) {
		public Boolean readObject(DataReader reader, int version) {
			return reader.readBoolean();
		}
		public void write(DataWriter writer, Boolean object) {
			writer.write(object.booleanValue());
		}
	};

	final static DataBinding<HashMap> HASH_MAP = new DataBinding<HashMap>(HashMap.class) {
		@SuppressWarnings("unchecked")
		public HashMap readObject(DataReader reader, int version) 
		throws UnknownBindingException {
			int count = reader.readInt();
			HashMap map = new HashMap(count*2);
			for (int i=0; i<count; i++) map.put(reader.readObject(), reader.readObject());
			return map;
		}
		public void write(DataWriter writer, HashMap object) {
			writer.write(object.size());
			for (Object obj : object.entrySet()) {
				Map.Entry entry = (Map.Entry)obj;
				writer.write(entry.getKey());
				writer.write(entry.getValue());
			}
		}
	};

	final Class<T> type;
	final int version;
	
	private DataBinding(Class<T> type)
	{
		this(type, type.getName());
		try {
			register();
		}
		catch (Exception e) {
			throw new Error(e);
		}
	}

	public DataBinding(Class<T> type, String name)
	{
		this(type, name, 1);
	}

	public DataBinding(Class<T> type, String name, int version)
	{
		super(name);
		this.type = type;
		this.version = version;
	}
	
	public abstract T readObject(DataReader reader, int version)
	throws UnknownBindingException;

	public abstract void write(DataWriter writer, T object);

	@Override
	protected void register() throws InvalidSchemaException
	{
		DataBinding current = NAME_MAP.get(name);
		if (current!=this) {
			if (current!=null) {
				throw new InvalidSchemaException("Data binding name already exists: "+name);
			}
			NAME_MAP.put(name, this);
			CLASS_MAP.put(type, this);
		}
	}
	
	public static DataBinding getBinding(Class objectClass)
	{
		return CLASS_MAP.get(objectClass);
	}
		
	public static DataBinding getBinding(String name)
	{
		return NAME_MAP.get(name);
	}

	public String getName() {
		return name;
	}
	
	public Class<T> getDataType()
	{
		return type;
	}

}
